package com.powernode.map.service.impl;


import com.powernode.common.constant.RedisConstant;
import com.powernode.common.constant.SystemConstant;
import com.powernode.common.util.LocationUtil;
import com.powernode.driver.client.DriverInfoFeignClient;
import com.powernode.map.repository.OrderServiceLocationRepository;
import com.powernode.map.service.LocationService;
import com.powernode.model.entity.driver.DriverSet;
import com.powernode.model.entity.map.OrderServiceLocation;
import com.powernode.model.form.map.OrderServiceLocationForm;
import com.powernode.model.form.map.SearchNearByDriverForm;
import com.powernode.model.form.map.UpdateDriverLocationForm;
import com.powernode.model.form.map.UpdateOrderLocationForm;
import com.powernode.model.vo.map.NearByDriverVo;
import com.powernode.model.vo.map.OrderLocationVo;
import com.powernode.model.vo.map.OrderServiceLastLocationVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private DriverInfoFeignClient driverInfoFeignClient;

    @Resource
    private OrderServiceLocationRepository orderServiceLocationRepository;


    @Resource
    private MongoTemplate mongoTemplate;

    /**
     * 更新配送员的位置信息
     */
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        //将配送员的位置信息存储到redis GEO中
        //将经纬度封装成Point对象
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(), updateDriverLocationForm.getLatitude().doubleValue());

        //将GEO数据保存到redis中
        redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION, point, updateDriverLocationForm.getDriverId().toString());

        return true;
    }


    /**
     * 配送员下班时，删除配送员位置信息
     */
    @Override
    public Boolean deleteDriverLocation(Long driverId) {
        //删除GEO数据
        redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION, driverId.toString());

        return true;
    }


    /**
     * 搜索附近符合条件的配送员
     */
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        //搜索起始位置方圆5公里之内的配送员

        //以起始位置为中心点
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(), searchNearByDriverForm.getLatitude().doubleValue());

        //定义距离：5公里
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);

        //基于point为中心点，以distance为半径，搜索GEO数据
        Circle circle = new Circle(point, distance);

        //构建参数
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeCoordinates() //坐标
                .includeDistance()//距离
                .sortAscending();//排序

        //发送命令
        GeoResults<RedisGeoCommands.GeoLocation<String>> result = redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);

        //获取集合信息
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();

        //构建返回结果对象
        List<NearByDriverVo> list = new ArrayList();

        if (!content.isEmpty()) {
            Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();

            while (iterator.hasNext()) {
                GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();

                //配送员id
                long driverId = Long.parseLong(item.getContent().getName());

                //当前距离
                double d = item.getDistance().getValue();
                BigDecimal currentDistance = new BigDecimal(d).setScale(2, RoundingMode.HALF_UP);

                //获取配送员个性化设置参数
                DriverSet driverSet = driverInfoFeignClient.getDriverSet(driverId).getData();
                //接单里程 配送员当前位置和订单起始位置的距离  ==0 不限制  3
                if (driverSet.getAcceptDistance().doubleValue() != 0 && driverSet.getAcceptDistance().subtract(currentDistance).doubleValue() < 0) {
                    //若进来则说明实际距离超出了配送员的接单距离
                    continue;
                }

                //订单里程  订单的起始位置和结束位置距离
                if (driverSet.getOrderDistance().doubleValue() != 0 && driverSet.getOrderDistance().subtract(searchNearByDriverForm.getMileageDistance()).doubleValue() < 0) {
                    //若进来则说明实际里程 起始位置到结束位置的距离超出了配送员的接单设置
                    continue;
                }

                //满足配送员接单条件

                NearByDriverVo nearByDriverVo = new NearByDriverVo();
                nearByDriverVo.setDriverId(driverId);
                nearByDriverVo.setDistance(currentDistance);
                list.add(nearByDriverVo);
            }
        }
        return list;
    }


    /**
     * 将位置信息传入redis
     */
    @Override
    public Boolean updateOrderLocation(UpdateOrderLocationForm updateOrderLocationForm) {
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());

        redisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId(), orderLocationVo);

        return true;
    }


    /**
     * 从redis查看配送员位置信息
     */
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        OrderLocationVo orderLocationVo = (OrderLocationVo)redisTemplate.opsForValue().get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);

        return orderLocationVo;
    }


    /**
     * 存储配送员实际配送的位置信息到mongodb中
     */
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> list) {
        List<OrderServiceLocation> locationList = new ArrayList<>();

        list.forEach(locationForm -> {
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
            BeanUtils.copyProperties(locationForm, orderServiceLocation);
            orderServiceLocation.setId(ObjectId.get().toString());//设置主键
            orderServiceLocation.setCreateTime(new Date());
            locationList.add(orderServiceLocation);
        });

        //将数据存入mongodb
        orderServiceLocationRepository.saveAll(locationList);

        return true;
    }


    /**
     * 获取配送员最后的位置信息
     */
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        //构建查询条件
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));
        //根据时间排序
        query.with(Sort.by(Sort.Order.desc("createTime")));
        //取最上面的1条数据
        query.limit(1);

        OrderServiceLocation orderServiceLocation = mongoTemplate.findOne(query, OrderServiceLocation.class);

        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        orderServiceLastLocationVo.setLatitude(orderServiceLocation.getLatitude());
        orderServiceLastLocationVo.setLongitude(orderServiceLocation.getLongitude());

        return orderServiceLastLocationVo;
    }


    /**
     * 计算配送员实际配送里程
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        //从mongodb中查出的配送坐标数据
        List<OrderServiceLocation> orderServiceLocationList = orderServiceLocationRepository.findByOrderIdOrderByCreateTimeAsc(orderId);

        //实际里程
        double realDistance = 0;

        //计算2个点的距离
        for (int i = 0,size=orderServiceLocationList.size() - 1; i < size; i++) {
            OrderServiceLocation location1 = orderServiceLocationList.get(i);
            OrderServiceLocation location2 = orderServiceLocationList.get(i + 1);

            //计算location1和location2的距离
            double distance = LocationUtil.getDistance(location1.getLatitude().doubleValue(), location1.getLongitude().doubleValue(), location2.getLatitude().doubleValue(), location2.getLongitude().doubleValue());

            realDistance += distance;
        }

        //由于我们的测试中配送员没有移动，所以结果是0，为了方便测试，这里做个假数据
        if (realDistance == 0) {
            return new BigDecimal(5);
        }

        return new BigDecimal(realDistance);

    }
}
